package ${packageName}.security;

import ${packageName}.config.TenantSecurityProperties;
import cn.hutool.core.date.DateUtil;
import cn.hutool.jwt.JWTPayload;
import cn.hutool.jwt.JWTUtil;
import cn.hutool.jwt.JWT;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 基于Hutool的JWT令牌工具
 */
@Component
@RequiredArgsConstructor
public class JwtTokenProvider {

    private static final String CLAIM_TENANT = "tenant";
    private static final String CLAIM_ROLES = "roles";
    private static final String CLAIM_TYPE = "tokenType";
    private static final String TYPE_ACCESS = "access";
    private static final String TYPE_REFRESH = "refresh";

    private final TenantSecurityProperties securityProperties;

    public String generateAccessToken(String tenantId, UserDetails userDetails) {
        long expiration = securityProperties.getJwt().getExpirationSeconds();
        return generateToken(tenantId, userDetails, expiration, TYPE_ACCESS);
    }

    public String generateRefreshToken(String tenantId, UserDetails userDetails) {
        long expiration = securityProperties.getJwt().getRefreshExpirationSeconds();
        return generateToken(tenantId, userDetails, expiration, TYPE_REFRESH);
    }

    public Authentication buildAuthentication(String token) {
        JWT jwt = parse(token);
        JWTPayload payload = jwt.getPayload();
        if (!Objects.equals(payload.getClaim(CLAIM_TYPE), TYPE_ACCESS)) {
            throw new IllegalArgumentException("仅允许使用访问令牌进行鉴权");
        }

        String username = Objects.toString(payload.getClaim(JWTPayload.SUBJECT), null);
        String tenantId = Objects.toString(payload.getClaim(CLAIM_TENANT), null);
        List<String> roles = resolveRoles(payload);

        List<SimpleGrantedAuthority> authorities = roles.stream()
            .map(SimpleGrantedAuthority::new)
            .collect(Collectors.toList());

        User principal = new User(username, token, authorities);
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(principal, token, authorities);
        authentication.setDetails(tenantId);
        return authentication;
    }

    public boolean validateAccessToken(String token) {
        return validateToken(token, TYPE_ACCESS);
    }

    public boolean validateRefreshToken(String token) {
        return validateToken(token, TYPE_REFRESH);
    }

    public String extractTenantId(String token) {
        return Objects.toString(parse(token).getPayload().getClaim(CLAIM_TENANT), null);
    }

    public String extractUsername(String token) {
        return Objects.toString(parse(token).getPayload().getClaim(JWTPayload.SUBJECT), null);
    }

    private String generateToken(String tenantId, UserDetails userDetails, long expirationSeconds, String tokenType) {
        Date issuedAt = new Date();
        Date expiresAt = new Date(issuedAt.getTime() + expirationSeconds * 1000);
        List<String> roles = userDetails.getAuthorities().stream()
            .map(SimpleGrantedAuthority::getAuthority)
            .collect(Collectors.toList());

        Map<String, Object> payload = new HashMap<>();
        payload.put(JWTPayload.SUBJECT, userDetails.getUsername());
        payload.put(CLAIM_TENANT, tenantId);
        payload.put(CLAIM_ROLES, roles);
        payload.put(CLAIM_TYPE, tokenType);
        payload.put(JWTPayload.ISSUED_AT, issuedAt);
        payload.put(JWTPayload.EXPIRES_AT, expiresAt);

        return JWTUtil.createToken(payload, secretKey());
    }

    private boolean validateToken(String token, String expectedType) {
        try {
            JWT jwt = parse(token);
            JWTPayload payload = jwt.getPayload();
            if (!Objects.equals(payload.getClaim(CLAIM_TYPE), expectedType)) {
                return false;
            }
            Date expiresAt = payload.getClaimsJson().getDate(JWTPayload.EXPIRES_AT);
            if (expiresAt != null && DateUtil.date(expiresAt).isBefore(DateUtil.date())) {
                return false;
            }
            return JWTUtil.verify(token, secretKey());
        } catch (Exception ex) {
            return false;
        }
    }

    private JWT parse(String token) {
        return JWTUtil.parseToken(token);
    }

    private byte[] secretKey() {
        return securityProperties.getJwt().getSecret().getBytes(StandardCharsets.UTF_8);
    }

    @SuppressWarnings("unchecked")
    private List<String> resolveRoles(JWTPayload payload) {
        Object value = payload.getClaim(CLAIM_ROLES);
        if (value instanceof List) {
            return (List<String>) value;
        }
        return Collections.emptyList();
    }
}
